﻿using System.Collections.Generic;
using Alone.GameObjects.Enemy;
using Alone.GameObjects.Objects;
using AloneLibrary;
using AloneLibrary.GameObjects;
using AloneLibrary.Services.Camera;
using AloneLibrary.Services.Input;
using AloneLibrary.Services.Sound;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;


namespace Alone.Handlers
{
    interface IBallisticsHandler 
    {
        void UpdateShot(GameTime time, Handgun weapon);
        HUD Hud { get; }
        void drawRay();
    }

    class BallisticsHandler : DrawableGameComponent, IBallisticsHandler
    {
        //for drawing ray
        private BasicEffect effect;
        private VertexDeclaration dec;
        private VertexPositionColor[] vertices;
        private HUD hud;
        private Game game;
        private ScreenManager screenManager;
        private Ray ray;
        private Vector3 rayPosition;
        private List<Enemy> hitEnemies = new List<Enemy>();
        private ICameraHandler camera;
        private IPlayerHandler playerHandler;
        private IInputHandler inputHandler;
        private IEnemyHandler enemyHandler;
        private IEnvironmentHandler environmentHandler;
        private ISoundManager soundHandler;
        private float prevRotation = 0.0f;
        private float range = 10.0f;
        private int fireRateDelay = 10;
        private int fireRateElapsed = 0;

        /// <summary>
        /// Gets or sets the hud.
        /// </summary>
        /// <value>The hud.</value>
        public HUD Hud {
            get { return hud; }
            set { hud = value; }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BallisticsHandler"/> class.
        /// </summary>
        /// <param name="game">The game.</param>
        /// <param name="screenManager">The screen manager.</param>
        public BallisticsHandler(Game game, ScreenManager screenManager)
            : base(game)
        {
            this.game = game;
            this.screenManager = screenManager;
            game.Services.AddService(typeof(IBallisticsHandler), this);
        }

        /// <summary>
        /// Initializes the component. Override this method to load any non-graphics resources and query for any required services.
        /// </summary>
        public override void Initialize()
        {
            this.camera = (ICameraHandler)game.Services.GetService(typeof(ICameraHandler));
            this.hud = new HUD(this.game.Content, this.screenManager.SpriteBatch);
            this.playerHandler = (IPlayerHandler)game.Services.GetService(typeof(IPlayerHandler));
            this.inputHandler = (IInputHandler)game.Services.GetService(typeof(IInputHandler));
            this.enemyHandler = (IEnemyHandler)game.Services.GetService(typeof(IEnemyHandler));
            this.environmentHandler = (IEnvironmentHandler)game.Services.GetService(typeof(IEnvironmentHandler));
            this.soundHandler = (ISoundManager)game.Services.GetService(typeof(ISoundManager));
          
            //for drawing rayline
            effect = new BasicEffect(game.GraphicsDevice, null);
            dec = new VertexDeclaration(game.GraphicsDevice, VertexPositionColor.VertexElements);

            base.Initialize();
        }

        /// <summary>
        /// Called when graphics resources need to be loaded. Override this method to load any component-specific graphics resources.
        /// </summary>
        protected override void LoadContent()
        {
            this.hud.addTextureSprite("ammo", "ammo", screenManager.GraphicsDevice.Viewport.Width - 100,
                screenManager.GraphicsDevice.Viewport.Height - 30, 0, 0.8f);
            this.hud.addFontSprite("ammo", screenManager.GraphicsDevice.Viewport.Width - 70,
                screenManager.GraphicsDevice.Viewport.Height - 50, playerHandler.Weapon.Ammo.ToString(), 0, 0.8f);
           
            base.LoadContent();
        }

        /// <summary>
        /// Called when the GameComponent needs to be updated. Override this method with component-specific update code.
        /// </summary>
        /// <param name="gameTime">Time elapsed since the last call to Update</param>
        public override void Update(GameTime gameTime)
        {
            if (this.prevRotation != camera.Rotation.X)
            {
                this.hud.updateSpriteRotation("Texture", "ammo", "ammo", this.prevRotation);
                this.hud.updateSpriteRotation("Font", "ammo", "ammo", this.prevRotation);
                this.prevRotation = camera.Rotation.X;
            }
            base.Update(gameTime);
        }

        /// <summary>
        /// Called when the DrawableGameComponent needs to be drawn. Override this method with component-specific drawing code. Reference page contains links to related conceptual articles.
        /// </summary>
        /// <param name="gameTime">Time passed since the last call to Draw.</param>
        public override void Draw(GameTime gameTime)
        {
            this.hud.drawSprites();
            base.Draw(gameTime);
        }

        /// <summary>
        /// Updates the shot.
        /// </summary>
        /// <param name="time">The time.</param>
        /// <param name="weapon">The weapon.</param>
        public void UpdateShot(GameTime time, Handgun weapon)
        {
            fireRateElapsed += time.ElapsedGameTime.Milliseconds;

            if (fireRateElapsed > fireRateDelay /* && weapon.Ammo > 0*/)
            {
                fireRateElapsed -= fireRateDelay;
                rayPosition = playerHandler.Player.Position;
                rayPosition.Y += 20;
                //rayPosition = new Vector3(playerHandler.Player.Position.X, 50.0f, playerHandler.Player.Position.Z);
 
                Matrix forwardMovement = Matrix.CreateRotationY(playerHandler.Player.Rotation.Y);
                Vector3 v = new Vector3(0, 0, -(range*1000));
               
                v = Vector3.Transform(v, forwardMovement);

                Vector3 newpos = rayPosition;
                newpos += Vector3.UnitZ * v.Z;
                newpos += Vector3.UnitY * v.Y;
                newpos += Vector3.UnitX * v.X;

                Vector3 direction = newpos - rayPosition;
                direction.Normalize();
                
                this.ray = new Ray(rayPosition, direction);

                weapon.Ammo--;
                this.hud.updateFontSprite("ammo", "ammo", weapon.Ammo.ToString());

                checkBulletHit(ray, newpos);
            }
        }

        /// <summary>
        /// Checks the bullet hit.
        /// </summary>
        /// <param name="ray">The ray.</param>
        /// <param name="fallbackPosition">The fallback position.</param>
        private void checkBulletHit(Ray ray, Vector3 fallbackPosition)
        {
            List<StaticGameObject> hitObjects = new List<StaticGameObject>();
            StaticGameObject closestObject = null;
            Enemy closestEnemy = null;

            float closestObjectDistance = 100000f;
            float closestEnemyDistance = 100000f;
            float length = 100000;

            foreach (Enemy enemy in enemyHandler.Enemies)
            {
                if (enemy.isCollidingRay(ray)) {
                    this.hitEnemies.Add(enemy);
                }
            }

            foreach (StaticGameObject obj in environmentHandler.GetObjectList)
            {
                if (obj.isCollidingRay(ray))
                    hitObjects.Add(obj);
            }

            if (hitObjects.Count > 0)
            {
                float objectDistance;

                foreach (StaticGameObject hitObject in hitObjects)
                {
                    if (hitObject is Ammo) break;

                    objectDistance = Vector3.Distance(playerHandler.Player.Position, hitObject.Position);
                    if (objectDistance < closestObjectDistance) //check the closest, if more than one
                    {
                        closestObjectDistance = objectDistance;
                        closestObject = hitObject;
                    }
                }
            }

            if (hitEnemies.Count > 0)
            {
                float enemyDistance;

                foreach (Enemy hitEnemy in hitEnemies)
                {
                    enemyDistance = Vector3.Distance(playerHandler.Player.Position, hitEnemy.Position);

                    if (enemyDistance < closestEnemyDistance) //check the closest, if more than one
                    {
                        closestEnemyDistance = enemyDistance;
                        closestEnemy = hitEnemy;
                    }
                }

                if (closestEnemyDistance < closestObjectDistance)
                {
                    playerHandler.Score += 100;
                    enemyHandler.Enemies.Remove(closestEnemy);
                    hitEnemies.Clear();
                    soundHandler.Play("laugh");
                }
            }

            Vector3 currentPos = fallbackPosition;
            if (closestEnemyDistance < closestObjectDistance)
            {
                currentPos = closestEnemy.Position - playerHandler.Player.Position;
                length = currentPos.Length();
            }
            else if (closestObjectDistance < closestEnemyDistance)
            {
                currentPos = closestObject.Position - playerHandler.Player.Position;
                length = currentPos.Length();
            }

            setUpRayVertices(this.rayPosition, this.ray.Direction, length);
        }

        /// <summary>
        /// Sets up ray vertices.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="direction">The direction.</param>
        /// <param name="length">The length.</param>
        public void setUpRayVertices(Vector3 position, Vector3 direction, float length)
        {
            vertices = new VertexPositionColor[2];

            direction.Normalize();
            Vector3 stopPos = position + (direction * length);

            vertices[0] = new VertexPositionColor(position, Color.Red);
            vertices[1] = new VertexPositionColor(stopPos, Color.Yellow);
        }

        /// <summary>
        /// Debug for see the shooting direction
        /// Drawing the shooting ray
        /// </summary>
        public void drawRay()
        {
            game.GraphicsDevice.VertexDeclaration = dec;
            effect.Begin();
            effect.VertexColorEnabled = true;
            effect.View = camera.View;
            effect.Projection = camera.Projection;

            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                game.GraphicsDevice.DrawUserPrimitives(PrimitiveType.LineList, vertices, 0, 1);
                pass.End();
            }
            effect.End();
        }

        /// <summary>
        /// Remotes the player shot.
        /// </summary>
        public void remotePlayerShot()
        {
            Vector3 position = playerHandler.RemotePlayer.Position;
            Matrix forwardMovement = Matrix.CreateRotationY(playerHandler.RemotePlayer.Rotation.Y);
            Vector3 v = new Vector3(0, 0, -(range * 1000));

            v = Vector3.Transform(v, forwardMovement);

            Vector3 newpos = position;
            newpos += Vector3.UnitZ * v.Z;
            newpos += Vector3.UnitY * v.Y;
            newpos += Vector3.UnitX * v.X;
            
            Ray ray = new Ray(position, newpos - position);
           
            checkBulletHit(ray, newpos);
        }
    }
}

